package club.xiaojiawei.graph;

import java.util.*;

/**
 * @author 肖嘉威
 * @version 1.0
 * @date 6/23/22 10:01 PM
 * @question 127. 单词接龙
 * @description 字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk：
 * 每一对相邻的单词只差一个字母。
 *  对于 1 <= i <= k 时，每个 si 都在 wordList 中。注意， beginWord 不需要在 wordList 中。
 * sk == endWord
 * 给你两个单词 beginWord 和 endWord 和一个字典 wordList ，返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列，返回 0 。
 */
public class LadderLength127 {

    public static void main(String[] args) {
        LadderLength127 test = new LadderLength127();
        int result = test.ladderLength2("hit", "cot", new ArrayList<>(List.of(new String[]{"hot", "dot", "dog", "lot", "log", "cot"})));
        System.out.println(result);
    }

    /**
     * 迪杰斯特拉算法
     * @param beginWord
     * @param endWord
     * @param wordList
     * @return
     */
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        wordList.add(beginWord);
        int size = wordList.size();
        int[][] graph = new int[size][size];
        char[][] chars = new char[size][size];
        int endIndex = -1;
        for (int i = 0; i < size; i++) {
            if (wordList.get(i).equals(endWord)){
                endIndex = i;
            }
            chars[i] = wordList.get(i).toCharArray();
        }
        if (endIndex == -1){
            return 0;
        }
        for (int i = 0; i < size; i++) {
            for (int j = 0; j <= i; j++){
                if (i != j){
                    int count = 1;
                    graph[i][j] = graph[j][i] = 1;
                    for (int k = 0; k < wordList.get(i).length(); k++){
                        if (chars[i][k] != chars[j][k] && count-- == 0){
                            graph[i][j] = graph[j][i] = size;
                            break;
                        }
                    }
                }
            }
        }
        boolean[] flag = new boolean[size];
        int[] distance = new int[size];
        Arrays.fill(distance, size);
        int current = size - 1;
        distance[current] = 0;
        int count = 1;
        int minIndex = -1, min;
        flag[size - 1] = true;
        while (count < size){
            min = size + 1;
            for (int i = 0; i < size; i ++){
                if (!flag[i] && current != i && graph[current][i] + distance[current] < distance[i]){
                    distance[i] = graph[current][i] + distance[current];
                }
            }
            for (int i = 0; i < size; i++){
                if (!flag[i] && distance[i] != 0 && distance[i] < min){
                    minIndex = i;
                    min = distance[i];
                }
            }
            flag[minIndex] = true;
            count++;
            current = minIndex;
        }
        return distance[endIndex] >= size? 0 : distance[endIndex] + 1;
    }

    /**
     * 官方-广度优先搜索 + 优化建图
     * @param beginWord
     * @param endWord
     * @param wordList
     * @return
     */
    public int ladderLength2(String beginWord, String endWord, List<String> wordList) {
        for (String word : wordList) {
            addEdge(word);
        }
        addEdge(beginWord);
        if (!wordId.containsKey(endWord)) {
            return 0;
        }
        int[] dis = new int[nodeNum];
        Arrays.fill(dis, Integer.MAX_VALUE);
        int beginId = wordId.get(beginWord), endId = wordId.get(endWord);
        dis[beginId] = 0;

        Queue<Integer> que = new LinkedList<Integer>();
        que.offer(beginId);
        while (!que.isEmpty()) {
            int x = que.poll();
            if (x == endId) {
                return dis[endId] / 2 + 1;
            }
            for (int it : edge.get(x)) {
                if (dis[it] == Integer.MAX_VALUE) {
                    dis[it] = dis[x] + 1;
                    que.offer(it);
                }
            }
        }
        return 0;
    }

    Map<String, Integer> wordId = new HashMap<String, Integer>();
    List<List<Integer>> edge = new ArrayList<List<Integer>>();
    int nodeNum = 0;


    public void addEdge(String word) {
        addWord(word);
        int id1 = wordId.get(word);
        char[] array = word.toCharArray();
        int length = array.length;
        for (int i = 0; i < length; ++i) {
            char tmp = array[i];
            array[i] = '*';
            String newWord = new String(array);
            addWord(newWord);
            int id2 = wordId.get(newWord);
            edge.get(id1).add(id2);
            edge.get(id2).add(id1);
            array[i] = tmp;
        }
    }

    public void addWord(String word) {
        if (!wordId.containsKey(word)) {
            wordId.put(word, nodeNum++);
            edge.add(new ArrayList<Integer>());
        }
    }

    /**
     * 官方-双向广度优先搜索
     * @param beginWord
     * @param endWord
     * @param wordList
     * @return
     */
    public int ladderLength3(String beginWord, String endWord, List<String> wordList) {
        for (String word : wordList) {
            addEdge2(word);
        }
        addEdge2(beginWord);
        if (!wordId.containsKey(endWord)) {
            return 0;
        }

        int[] disBegin = new int[nodeNum];
        Arrays.fill(disBegin, Integer.MAX_VALUE);
        int beginId = wordId.get(beginWord);
        disBegin[beginId] = 0;
        Queue<Integer> queBegin = new LinkedList<Integer>();
        queBegin.offer(beginId);

        int[] disEnd = new int[nodeNum];
        Arrays.fill(disEnd, Integer.MAX_VALUE);
        int endId = wordId.get(endWord);
        disEnd[endId] = 0;
        Queue<Integer> queEnd = new LinkedList<Integer>();
        queEnd.offer(endId);

        while (!queBegin.isEmpty() && !queEnd.isEmpty()) {
            int queBeginSize = queBegin.size();
            for (int i = 0; i < queBeginSize; ++i) {
                int nodeBegin = queBegin.poll();
                if (disEnd[nodeBegin] != Integer.MAX_VALUE) {
                    return (disBegin[nodeBegin] + disEnd[nodeBegin]) / 2 + 1;
                }
                for (int it : edge.get(nodeBegin)) {
                    if (disBegin[it] == Integer.MAX_VALUE) {
                        disBegin[it] = disBegin[nodeBegin] + 1;
                        queBegin.offer(it);
                    }
                }
            }

            int queEndSize = queEnd.size();
            for (int i = 0; i < queEndSize; ++i) {
                int nodeEnd = queEnd.poll();
                if (disBegin[nodeEnd] != Integer.MAX_VALUE) {
                    return (disBegin[nodeEnd] + disEnd[nodeEnd]) / 2 + 1;
                }
                for (int it : edge.get(nodeEnd)) {
                    if (disEnd[it] == Integer.MAX_VALUE) {
                        disEnd[it] = disEnd[nodeEnd] + 1;
                        queEnd.offer(it);
                    }
                }
            }
        }
        return 0;
    }

    public void addEdge2(String word) {
        addWord2(word);
        int id1 = wordId.get(word);
        char[] array = word.toCharArray();
        int length = array.length;
        for (int i = 0; i < length; ++i) {
            char tmp = array[i];
            array[i] = '*';
            String newWord = new String(array);
            addWord2(newWord);
            int id2 = wordId.get(newWord);
            edge.get(id1).add(id2);
            edge.get(id2).add(id1);
            array[i] = tmp;
        }
    }

    public void addWord2(String word) {
        if (!wordId.containsKey(word)) {
            wordId.put(word, nodeNum++);
            edge.add(new ArrayList<Integer>());
        }
    }

    /**
     * 民间-
     * @param beginWord
     * @param endWord
     * @param wordList
     * @return
     */
    public int ladderLength4(String beginWord, String endWord, List<String> wordList) {
        for (String wordStr : wordList) {
            addWord3(wordStr);
        }
        if (!findWords(endWord)) {
            return 0;
        }

        HashSet<String> beginSet = new HashSet<>();
        HashSet<String> endSet = new HashSet<>();
        beginSet.add(beginWord);
        endSet.add(endWord);

        int step = 1;
        while (!beginSet.isEmpty() && !endSet.isEmpty()) {
            if (beginSet.size() > endSet.size()) {
                HashSet<String> tmp = beginSet;
                beginSet = endSet;
                endSet = tmp;
            }
            HashSet<String> nextSet = new HashSet<>();
            for (String word : beginSet) {
                if (endSet.contains(word)) {
                    return step;
                } else {
                    removeWord(word);
                }
            }
            for (String word : beginSet) {
                HashSet<String> ans = findWordsNext(word);
                nextSet.addAll(ans);
            }
            beginSet = nextSet;
            step++;
        }
        return 0;
    }

    static class TrieNode {
        TrieNode[] child = new TrieNode[26];
        int childCount = 0;
        String word = null;
    }

    TrieNode head = new TrieNode();

    private void addWord3(String wordStr) {
        if (wordStr == null || wordStr.length() == 0) {
            return;
        }
        char[] word = wordStr.toCharArray();
        TrieNode cur = head;
        int i = 0;
        while (i < word.length) {
            int index = word[i] - 'a';
            if (cur.child[index] == null) {
                cur.child[index] = new TrieNode();
                cur.childCount++;
            }
            cur = cur.child[index];
            i++;
        }
        cur.word = wordStr;
    }

    private boolean findWords(String wordStr) {
        char[] word = wordStr.toCharArray();
        HashSet<String> ans = new HashSet<>();
        innerFind(word, -1, ans);
        return ans.size() > 0;
    }

    private HashSet<String> findWordsNext(String wordStr) {
        char[] word = wordStr.toCharArray();
        HashSet<String> ans = new HashSet<>();
        for (int i = 0; i < word.length; i++) {
            innerFind(word, i, ans);
        }
        return ans;
    }

    private void innerFind(char[] word, int pos, HashSet<String> ans) {
        ArrayDeque<TrieNode> deque = new ArrayDeque<>();
        deque.add(head);
        int i = 0;
        while (i < word.length && !deque.isEmpty()) {
            int index = word[i] - 'a';
            int size = deque.size();
            while (size-- > 0) {
                TrieNode tmp = deque.removeFirst();
                if (i == pos) {
                    for (int j = 0; j < 26; j++) {
                        if (j != index && tmp.child[j] != null) {
                            deque.add(tmp.child[j]);
                        }
                    }
                } else {
                    if (tmp.child[index] != null) {
                        deque.add(tmp.child[index]);
                    }
                }
            }
            i++;
        }
        if (!deque.isEmpty()) {
            for (TrieNode tmp : deque) {
                if (tmp.word != null) {
                    ans.add(tmp.word);
                }
            }
        }
    }

    private void removeWord(String wordStr) {
        char[] word = wordStr.toCharArray();
        innerRemoveWord(word, 0, head);
    }

    private boolean innerRemoveWord(char[] word, int i, TrieNode t) {
        if (t == null || word == null || i >= word.length) {
            return false;
        }
        int nextIndex = word[i] - 'a';
        if (t.child[nextIndex] == null) {
            return false;
        }
        if (i == word.length - 1) {
            if (t.child[nextIndex].childCount == 0) {
                t.child[nextIndex] = null;
                t.childCount--;
            } else {
                t.child[nextIndex].word = null;
            }
            return t.childCount == 0 && t.word == null;
        }
        boolean canRemoveChild = innerRemoveWord(word, i + 1, t.child[nextIndex]);
        if (canRemoveChild) {
            t.child[nextIndex] = null;
            t.childCount--;
            return t.childCount == 0 && t.word == null;
        } else {
            return false;
        }
    }

}


